package org.anachronos.jeep.ui.trace;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.anachronos.jeep.model.trace.DocumentRoot;
import org.anachronos.jeep.model.trace.Invocation;
import org.anachronos.jeep.model.trace.TracePackage;
import org.anachronos.jeep.model.trace.util.BacktraceMapBuilder;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.ui.viewer.IViewerProvider;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.databinding.EMFProperties;
import org.eclipse.emf.databinding.FeaturePath;
import org.eclipse.emf.databinding.IEMFListProperty;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.edit.ui.util.EditUIUtil;
import org.eclipse.jface.databinding.viewers.ObservableListTreeContentProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.widgets.FormToolkit;


public class TraceEditor extends FormEditor implements ISelectionProvider {
	public static final String ID = "org.anachronos.jeep.ui.editor.trace";
	
	private ResourceSetImpl resourceSet;
	private Resource resource;
	private TraceTablePage invocationsPage;
	private TraceTreePage callStackPage;
	private TraceTreePage threadsPage;
	private BacktracePage backTracePage;
	
	private Collection<ISelectionChangedListener> selectionChangedListeners 
		= new ArrayList<ISelectionChangedListener>();

	private ISelection selection = StructuredSelection.EMPTY;

	private ISelectionChangedListener selectionChangeListener;

	public TraceEditor() {
	}
	
	@Override
	protected FormToolkit createToolkit(Display display) {
		return super.createToolkit(display);
	}
	
	@Override
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		super.init(site, input);
		getSite().setSelectionProvider(this);
		final URI resourceURI = EditUIUtil.getURI(getEditorInput());
		resourceSet = new ResourceSetImpl();
		resource = resourceSet.createResource(resourceURI);
		setPartName(resourceURI.segment(resourceURI.segmentCount() - 1));
		try {
			((ResourceImpl)resource).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>());
			final HashMap<Object, Object> options = new HashMap<Object, Object>();
			options.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true);
			resource.load(options);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected void addPages() {
		try {
			final DocumentRoot documentRoot = (DocumentRoot) resource.getContents().get(0);
			invocationsPage = new TraceTablePage(this, "Invocations", "Invocations");
			final IEMFListProperty statementsObservables = 
				EMFProperties.multiList(FeaturePath.fromList(TracePackage.Literals.DOCUMENT_ROOT__TRACE, 
						TracePackage.Literals.TRACE__MERGED_SQL_STATEMENT_EXECUTIONS,
						TracePackage.Literals.MERGED_SQL_STATEMENT_EXECUTIONS_TYPE__MERGED_SQL_STATEMENT_EXECUTIONS),
						FeaturePath.fromList(TracePackage.Literals.DOCUMENT_ROOT__TRACE, 
								TracePackage.Literals.TRACE__MERGED_METHOD_INVOCATIONS,
								TracePackage.Literals.MERGED_METHOD_INVOCATIONS_TYPE__MERGED_METHOD_INVOCATIONS));
			invocationsPage.setInput(statementsObservables.observe(documentRoot));
			addPage(invocationsPage);
			callStackPage = new TraceTreePage(this, "Callstack", "Callstack", 3);
			callStackPage.setInput(documentRoot);
			final ObservableListTreeContentProvider callStackContentProvider = new ObservableListTreeContentProvider(
					TraceTreeFactory.createCallStackTreeFactory(callStackPage.getViewerFilter()), 
					new TraceTreeStructureAdvisor());
			callStackPage.setContentProvider(callStackContentProvider);
			addPage(callStackPage);
			threadsPage = new TraceTreePage(this, "Threads", "Threads");
			final ObservableListTreeContentProvider threadsContentProvider = new ObservableListTreeContentProvider(
					TraceTreeFactory.createTraceThreadTreeFactory(threadsPage.getViewerFilter()), 
					new TraceTreeStructureAdvisor());
			threadsPage.setContentProvider(threadsContentProvider);
			threadsPage.setInput(documentRoot);
			addPage(threadsPage);
			
			final Map<String, List<Invocation>> backTraceMap = new BacktraceMapBuilder().
				build(documentRoot.getTrace().getMergedCallStack());
			backTracePage = new BacktracePage(this, "Backtrace", "Backtrace", backTraceMap);
			addPage(backTracePage);
		} catch (final PartInitException e) {
			throw new RuntimeException(e);
		}
	}

	@Override
	protected void pageChange(int newPageIndex) {
		if (selectionChangeListener == null) {
			selectionChangeListener = new ISelectionChangedListener() {
				@Override
				public void selectionChanged(final SelectionChangedEvent event) {
					setSelection(event.getSelection());
				}
			};
		}
		final IViewerProvider oldViewerProvider = (IViewerProvider) getActivePageInstance();
		if (oldViewerProvider != null && oldViewerProvider.getViewer() != null) {
			final Viewer oldViewer = oldViewerProvider.getViewer();
			oldViewer.removeSelectionChangedListener(selectionChangeListener);
		}
		super.pageChange(newPageIndex);
		final IViewerProvider newViewerProvider = 
			(IViewerProvider) getActivePageInstance();
		final Viewer newViewer = newViewerProvider.getViewer();
		newViewer.addSelectionChangedListener(selectionChangeListener);
	}
	
	public BacktracePage getBackTracePage() {
		return backTracePage;
	}
	
	@Override
	public void doSave(IProgressMonitor monitor) {
	}

	@Override
	public void doSaveAs() {
	}

	@Override
	public boolean isSaveAsAllowed() {
		return false;
	}

	@Override
	public void addSelectionChangedListener(final ISelectionChangedListener listener) {
		selectionChangedListeners.add(listener);
	}

	@Override
	public ISelection getSelection() {
		return selection;
	}

	@Override
	public void removeSelectionChangedListener(final ISelectionChangedListener listener) {
		selectionChangedListeners.remove(listener);
	}

	@Override
	public void setSelection(final ISelection selection) {
		this.selection = selection;
		for (final ISelectionChangedListener listener : selectionChangedListeners) {
			listener.selectionChanged(new SelectionChangedEvent(this, selection));
		}
	}
}
